from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.utils import timezone
from datetime import datetime, timedelta
import json
import re
from docx import Document
import io

from .models import ExperimentPlan, ExperimentDocument
from personnel.models import Equipment
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
import os
from .serializers import (
    ExperimentPlanSerializer,
    ExperimentPlanCreateSerializer,
    ExperimentPlanListSerializer
)


class ExperimentPlanViewSet(viewsets.ModelViewSet):
    """试验计划视图集"""
    queryset = ExperimentPlan.objects.all()
    serializer_class = ExperimentPlanSerializer
    
    def get_queryset(self):
        """获取查询集"""
        queryset = ExperimentPlan.objects.all()
        
        # 过滤参数
        equipment_id = self.request.query_params.get('equipment_id', None)
        experiment_type = self.request.query_params.get('experiment_type', None)
        status_filter = self.request.query_params.get('status', None)
        source = self.request.query_params.get('source', None)
        start_date = self.request.query_params.get('start_date', None)
        end_date = self.request.query_params.get('end_date', None)
        
        if equipment_id:
            queryset = queryset.filter(equipment_id=equipment_id)
        if experiment_type:
            queryset = queryset.filter(experiment_type=experiment_type)
        if status_filter:
            queryset = queryset.filter(status=status_filter)
        if source:
            queryset = queryset.filter(source=source)
        if start_date:
            queryset = queryset.filter(enter_date__gte=start_date)
        if end_date:
            queryset = queryset.filter(end_date__lte=end_date)
        
        return queryset.order_by('enter_date', 'start_date')
    
    def get_serializer_class(self):
        """根据动作选择序列化器"""
        if self.action == 'list':
            return ExperimentPlanListSerializer
        elif self.action in ['create_from_external']:
            return ExperimentPlanCreateSerializer
        return ExperimentPlanSerializer
    
    @action(detail=False, methods=['get'])
    def gantt_data(self, request):
        """获取甘特图数据"""
        queryset = self.get_queryset()
        
        # 按试验类型分组（支持中文和英文类型）
        satellite_experiments = queryset.filter(experiment_type__in=['satellite', '整星'])
        component_experiments = queryset.filter(experiment_type__in=['component', '单机'])
        custom_experiments = queryset.filter(experiment_type__in=['custom', '自建'])
        
        satellite_serializer = ExperimentPlanListSerializer(satellite_experiments, many=True)
        component_serializer = ExperimentPlanListSerializer(component_experiments, many=True)
        custom_serializer = ExperimentPlanListSerializer(custom_experiments, many=True)
        
        return Response({
            '整星试验': satellite_serializer.data,
            '单机试验': component_serializer.data,
            '自建试验': custom_serializer.data
        })
    
    @action(detail=False, methods=['post'])
    def sync_from_scheduling_system(self, request):
        """从总装调度管理系统同步数据"""
        try:
            # 从请求中获取调度系统数据
            scheduling_data = request.data
            
            created_count = 0
            updated_count = 0
            errors = []
            
            # 如果是单个对象，转换为列表
            if not isinstance(scheduling_data, list):
                scheduling_data = [scheduling_data]
            
            for item in scheduling_data:
                try:
                    # 检查是否已存在（基于external_id）
                    existing = ExperimentPlan.objects.filter(
                        external_id=item.get('external_id'),
                        source='scheduling_system'
                    ).first()
                    
                    serializer = ExperimentPlanCreateSerializer(data=item)
                    if serializer.is_valid():
                        if existing:
                            # 更新现有记录
                            for key, value in serializer.validated_data.items():
                                if key != 'equipment_name':
                                    setattr(existing, key, value)
                            existing.save()
                            updated_count += 1
                        else:
                            # 创建新记录
                            serializer.save()
                            created_count += 1
                    else:
                        errors.append({
                            'external_id': item.get('external_id'),
                            'errors': serializer.errors
                        })
                except Exception as e:
                    errors.append({
                        'external_id': item.get('external_id'),
                        'errors': str(e)
                    })
            
            return Response({
                'success': True,
                'created_count': created_count,
                'updated_count': updated_count,
                'errors': errors
            })
        
        except Exception as e:
            return Response({
                'msg': 'error',
                'code': '1'
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    
    @action(detail=False, methods=['post'])
    def sync_from_dingtalk(self, request):
        """从钉钉预约系统同步数据"""
        try:
            # 获取钉钉发送的数据
            dingtalk_data = None
            
            # 根据Content-Type处理不同格式的数据
            if request.content_type and 'multipart/form-data' in request.content_type:
                dingtalk_data = dict(request.POST)
                if request.FILES:
                    dingtalk_data.update(dict(request.FILES))
            elif request.POST:
                dingtalk_data = dict(request.POST)
            elif request.GET:
                dingtalk_data = dict(request.GET)
            else:
                # 使用DRF的request.data作为默认方式
                dingtalk_data = request.data
            
            # 如果仍然没有数据，返回错误
            if not dingtalk_data:
                return Response({
                    'msg': 'error',
                    'code': '1',
                    'error': '没有接收到有效数据'
                }, status=status.HTTP_400_BAD_REQUEST)
            
            # 处理multipart/form-data格式的数据，将列表值转换为单个值
            if isinstance(dingtalk_data, dict):
                processed_data = {}
                for key, value in dingtalk_data.items():
                    if isinstance(value, list) and len(value) > 0:
                        # 如果是列表且不为空，取第一个元素
                        if key == 'file':
                            # 文件字段保持列表格式
                            processed_data[key] = value
                        else:
                            # 其他字段取第一个元素
                            processed_data[key] = value[0]
                    else:
                        processed_data[key] = value
                dingtalk_data = processed_data
            
            print(f"钉钉发送的数据: {dingtalk_data}")
            
            # 检查是否已存在（基于bid）
            existing_plans = ExperimentPlan.objects.filter(
                bid=dingtalk_data.get('bid'),
                source='dingtalk'
            )
            
            if existing_plans.exists():
                # 删除现有记录，重新创建
                existing_plans.delete()
            
            # 首先收集所有委托单的试验项目名称
            experiment_report_names = []
            
            # 处理文件上传，提取所有委托单信息
            if 'file' in dingtalk_data and dingtalk_data['file']:
                files = dingtalk_data['file']
                if not isinstance(files, list):
                    files = [files]
                
                for uploaded_file in files:
                    try:
                        # 检查文件名是否包含'委托单'，如果包含则解析试验项目名称
                        if '委托单' in uploaded_file.name and uploaded_file.name.lower().endswith(('.docx', '.doc')):
                            try:
                                # 读取文件内容
                                file_content = uploaded_file.read()
                                
                                # 解析试验项目名称
                                experiment_report_name = self._extract_experiment_report_name_from_docx(file_content)
                                
                                if experiment_report_name:
                                    experiment_report_names.append(experiment_report_name)
                                    print(f"成功提取试验项目名称: {experiment_report_name}")
                                else:
                                    print(f"未能从文档 {uploaded_file.name} 中提取到试验项目名称")
                                    
                            except Exception as parse_error:
                                print(f"解析委托单文档时出错: {str(parse_error)}")
                        
                        print(f"成功处理文档: {uploaded_file.name}, 大小: {uploaded_file.size} 字节")
                        
                    except Exception as file_error:
                        print(f"处理文件时出错: {str(file_error)}")
                        continue
            
            # 将所有试验项目名称用分号连接，用于不可调整的任务块
            combined_experiment_names = '; '.join(experiment_report_names) if experiment_report_names else ''
            
            # 获取所有涉及的设备
            equipments = self._extract_all_equipments_from_details(dingtalk_data.get('details', ''))
            
            # 为每个设备创建试验计划
            created_plans = []
            
            for equipment in equipments:
                # 数据转换和映射（为每个设备单独处理）
                base_data = self._transform_dingtalk_data_for_equipment(dingtalk_data, equipment)
                print(f"设备 {equipment.name} 转换后的数据: {base_data}")
                
                # 创建两条记录：一条可调整，一条不可调整
                adjustable_data = base_data.copy()
                adjustable_data['is_adjustable'] = True
                
                non_adjustable_data = base_data.copy()
                non_adjustable_data['is_adjustable'] = False
                
                # 根据设备数量和委托单数量决定experiment_report_name的填充策略
                if len(equipments) == 1 and len(experiment_report_names) == 1:
                    # 一个设备一个委托单：两种任务块都填入委托单信息
                    adjustable_data['experiment_report_name'] = combined_experiment_names
                    non_adjustable_data['experiment_report_name'] = combined_experiment_names
                else:
                    # 多设备或多委托单：可调整任务块保持为空，不可调整任务块填入所有委托单信息
                    non_adjustable_data['experiment_report_name'] = combined_experiment_names
                
                # 创建记录
                adjustable_plan = ExperimentPlan.objects.create(**adjustable_data)
                non_adjustable_plan = ExperimentPlan.objects.create(**non_adjustable_data)
                
                created_plans.extend([adjustable_plan, non_adjustable_plan])
                
                print(f"设备 {equipment.name} 的试验计划已创建")
                if len(equipments) == 1 and len(experiment_report_names) == 1:
                    print(f"  - 一个设备一个委托单：可调整和不可调整任务块的 experiment_report_name 都为 '{combined_experiment_names}'")
                else:
                    print(f"  - 多设备或多委托单：可调整任务块 experiment_report_name 为空，不可调整任务块为 '{combined_experiment_names}'")
            
            # 为第一个可调整的试验计划关联文档（如果有的话）
            if created_plans and 'file' in dingtalk_data and dingtalk_data['file']:
                files = dingtalk_data['file']
                if not isinstance(files, list):
                    files = [files]
                
                # 找到第一个可调整的试验计划
                first_adjustable_plan = None
                for plan in created_plans:
                    if plan.is_adjustable:
                        first_adjustable_plan = plan
                        break
                
                if first_adjustable_plan:
                    for uploaded_file in files:
                        try:
                            # 创建文档记录，关联到第一个可调整的试验计划
                            document = ExperimentDocument.objects.create(
                                experiment_plan=first_adjustable_plan,
                                file_name=uploaded_file.name,
                                file_size=uploaded_file.size,
                                file_type=self._get_file_type(uploaded_file.name),
                                dingtalk_file_id=dingtalk_data.get('bid', ''),  # 使用bid作为文件标识
                                upload_source='dingtalk'
                            )
                            print(f"成功保存文档: {uploaded_file.name}")
                            
                        except Exception as file_error:
                            print(f"保存文件时出错: {str(file_error)}")
                            continue
            
            return Response({
                'msg': 'success',
                'code': '0'
            })
        
        except Exception as e:
            return Response({
                'success': False,
                'error': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    
    def _get_file_type(self, filename):
        """根据文件名获取文件类型"""
        if not filename:
            return 'unknown'
        
        extension = filename.lower().split('.')[-1] if '.' in filename else ''
        
        file_type_mapping = {
            'doc': 'document',
            'docx': 'document', 
            'pdf': 'document',
            'txt': 'document',
            'xls': 'spreadsheet',
            'xlsx': 'spreadsheet',
            'ppt': 'presentation',
            'pptx': 'presentation',
            'jpg': 'image',
            'jpeg': 'image',
            'png': 'image',
            'gif': 'image',
            'zip': 'archive',
            'rar': 'archive',
            '7z': 'archive'
        }
        
        return file_type_mapping.get(extension, 'other')
    
    def _extract_experiment_report_name_from_docx(self, file_content):
        """从Word文档中提取试验项目（报告）名称"""
        try:
            # 将文件内容转换为BytesIO对象
            file_stream = io.BytesIO(file_content)
            doc = Document(file_stream)
            
            # 遍历文档中的所有段落
            for paragraph in doc.paragraphs:
                text = paragraph.text.strip()
                if not text:
                    continue
                
                # 查找包含"试验项目"或"报告名称"等关键词的行
                if any(keyword in text for keyword in ['试验项目', '报告名称', '项目名称', '试验名称']):
                    # 使用正则表达式提取名称
                    # 匹配冒号后面的内容
                    match = re.search(r'[:：]\s*(.+)', text)
                    if match:
                        return match.group(1).strip()
                    
                    # 如果没有冒号，尝试提取关键词后面的内容
                    for keyword in ['试验项目', '报告名称', '项目名称', '试验名称']:
                        if keyword in text:
                            # 提取关键词后面的内容
                            parts = text.split(keyword, 1)
                            if len(parts) > 1:
                                result = parts[1].strip().lstrip('：:').strip()
                                if result:
                                    return result
            
            # 遍历文档中的表格
            for table in doc.tables:
                for row in table.rows:
                    for cell_index, cell in enumerate(row.cells):
                        text = cell.text.strip()
                        if any(keyword in text for keyword in ['试验项目', '报告名称', '项目名称', '试验名称']):
                            # 在表格中查找对应的值
                            # 如果当前单元格包含关键词，检查下一个单元格或同行其他单元格
                            if cell_index + 1 < len(row.cells):
                                next_cell_text = row.cells[cell_index + 1].text.strip()
                                if next_cell_text and not any(kw in next_cell_text for kw in ['试验项目', '报告名称', '项目名称', '试验名称']):
                                    return next_cell_text
            
            return None
            
        except Exception as e:
            print(f"解析Word文档失败: {str(e)}")
            return None
    
    def _transform_dingtalk_data(self, dingtalk_data):
        """转换钉钉数据格式为模型字段"""
        from datetime import datetime
        
        # 从details中提取设备信息并进行模糊匹配
        equipment = self._find_equipment_by_fuzzy_match(dingtalk_data.get('details', ''))
        
        # 映射试验类型
        experiment_type_mapping = {
            '单机': '单机',
            '整星': '整星',
            '单机试验': 'component',
            '整星试验': 'satellite'
        }
        
        experiment_type = experiment_type_mapping.get(
            dingtalk_data.get('type', ''), 
            '单机'  # 默认值
        )
        
        # 日期格式转换函数
        def parse_date(date_str):
            """将字符串日期转换为日期对象"""
            if not date_str:
                return None
            try:
                return datetime.strptime(date_str, '%Y-%m-%d').date()
            except (ValueError, TypeError):
                print(f"日期格式转换失败: {date_str}")
                return None
        
        # 转换数据 - 直接使用钉钉发送的字段名
        transformed = {
            'experiment_type': experiment_type,
            'equipment': equipment,
            'enter_date': parse_date(dingtalk_data.get('startdate')),
            'start_date': parse_date(dingtalk_data.get('startdate')),
            'complete_date': parse_date(dingtalk_data.get('enddate')),
            'end_date': parse_date(dingtalk_data.get('enddate')),
            'source': 'dingtalk',
            # 钉钉特有字段 - 直接映射
            'project': dingtalk_data.get('project'),
            'depart': dingtalk_data.get('depart'),
            'days': dingtalk_data.get('days'),
            'bid': dingtalk_data.get('bid'),
            'addr': dingtalk_data.get('addr'),
            'details': dingtalk_data.get('details')
        }
        
        return transformed
    
    def _extract_equipment_from_details(self, details_json):
        """从details JSON中提取设备名称"""
        try:
            if not details_json:
                return None
            
            # 处理钉钉发送的数据格式：details可能是字符串或列表
            if isinstance(details_json, list):
                # 如果是列表，取第一个元素作为JSON字符串
                if len(details_json) > 0:
                    details_str = details_json[0]
                else:
                    return None
            else:
                # 如果是字符串，直接使用
                details_str = details_json
            
            # 解析JSON字符串
            details = json.loads(details_str)
            if isinstance(details, list) and len(details) > 0:
                row_value = details[0].get('rowValue', [])
                for item in row_value:
                    if item.get('label') == '试验设备':
                        return item.get('value')
            return None
        except (json.JSONDecodeError, KeyError, IndexError, TypeError) as e:
            print(f"提取设备名称失败: {e}")
            return None
    
    def _find_equipment_by_fuzzy_match(self, details_json):
        """从details中提取设备信息并进行模糊匹配"""
        # 首先提取设备名称
        equipment_name = self._extract_equipment_from_details(details_json)
        
        if not equipment_name:
            # 如果无法提取设备名称，创建默认设备
            equipment, created = Equipment.objects.get_or_create(
                name='默认设备',
                defaults={'responsible': '系统自动创建'}
            )
            return equipment
        
        # 尝试精确匹配
        try:
            equipment = Equipment.objects.get(name=equipment_name)
            return equipment
        except Equipment.DoesNotExist:
            pass
        
        # 进行模糊匹配
        all_equipments = Equipment.objects.all()
        for equipment in all_equipments:
            # 检查设备名称是否包含提取的名称，或者提取的名称是否包含设备名称
            if (equipment_name.lower() in equipment.name.lower() or 
                equipment.name.lower() in equipment_name.lower()):
                return equipment
        
        # 如果没有匹配到，创建新设备
        equipment, created = Equipment.objects.get_or_create(
            name=equipment_name,
            defaults={'responsible': '钉钉同步创建'}
        )
        return equipment
    
    def _extract_all_equipments_from_details(self, details_json):
        """从details中提取所有设备信息"""
        try:
            if not details_json:
                # 如果没有details，返回默认设备
                equipment, created = Equipment.objects.get_or_create(
                    name='默认设备',
                    defaults={'responsible': '系统自动创建'}
                )
                return [equipment]
            
            # 处理钉钉发送的数据格式：details可能是字符串或列表
            if isinstance(details_json, list):
                if len(details_json) > 0:
                    details_str = details_json[0]
                else:
                    equipment, created = Equipment.objects.get_or_create(
                        name='默认设备',
                        defaults={'responsible': '系统自动创建'}
                    )
                    return [equipment]
            else:
                details_str = details_json
            
            # 解析JSON字符串
            details = json.loads(details_str)
            equipment_names = []
            
            if isinstance(details, list) and len(details) > 0:
                row_value = details[0].get('rowValue', [])
                for item in row_value:
                    if item.get('label') == '试验设备':
                        # 设备名称可能包含多个设备，用逗号或分号分隔
                        equipment_value = item.get('value', '')
                        if equipment_value:
                            # 分割设备名称（支持逗号、分号、换行符分隔）
                            import re
                            equipment_list = re.split(r'[,，;；\n\r]+', equipment_value)
                            equipment_names.extend([name.strip() for name in equipment_list if name.strip()])
            
            if not equipment_names:
                # 如果没有提取到设备名称，返回默认设备
                equipment, created = Equipment.objects.get_or_create(
                    name='默认设备',
                    defaults={'responsible': '系统自动创建'}
                )
                return [equipment]
            
            # 为每个设备名称查找或创建设备对象
            equipments = []
            for equipment_name in equipment_names:
                # 尝试精确匹配
                equipment = None
                try:
                    equipment = Equipment.objects.get(name=equipment_name)
                except Equipment.DoesNotExist:
                    # 进行模糊匹配
                    all_equipments = Equipment.objects.all()
                    for existing_equipment in all_equipments:
                        if (equipment_name.lower() in existing_equipment.name.lower() or 
                            existing_equipment.name.lower() in equipment_name.lower()):
                            equipment = existing_equipment
                            break
                
                # 如果没有匹配到，创建新设备
                if not equipment:
                    equipment, created = Equipment.objects.get_or_create(
                        name=equipment_name,
                        defaults={'responsible': '钉钉同步创建'}
                    )
                
                equipments.append(equipment)
            
            return equipments
            
        except (json.JSONDecodeError, KeyError, IndexError, TypeError) as e:
            print(f"提取设备信息失败: {e}")
            # 返回默认设备
            equipment, created = Equipment.objects.get_or_create(
                name='默认设备',
                defaults={'responsible': '系统自动创建'}
            )
            return [equipment]
    
    def _transform_dingtalk_data_for_equipment(self, dingtalk_data, equipment):
        """为特定设备转换钉钉数据格式为模型字段"""
        from datetime import datetime
        
        # 映射试验类型
        experiment_type_mapping = {
            '单机': '单机',
            '整星': '整星',
            '单机试验': 'component',
            '整星试验': 'satellite'
        }
        
        experiment_type = experiment_type_mapping.get(
            dingtalk_data.get('type', ''), 
            '单机'  # 默认值
        )
        
        # 日期格式转换函数
        def parse_date(date_str):
            """将字符串日期转换为日期对象"""
            if not date_str:
                return None
            try:
                return datetime.strptime(date_str, '%Y-%m-%d').date()
            except (ValueError, TypeError):
                print(f"日期格式转换失败: {date_str}")
                return None
        
        # 转换数据 - 直接使用钉钉发送的字段名
        transformed = {
            'experiment_type': experiment_type,
            'equipment': equipment,  # 使用传入的特定设备
            'enter_date': parse_date(dingtalk_data.get('startdate')),
            'start_date': parse_date(dingtalk_data.get('startdate')),
            'complete_date': parse_date(dingtalk_data.get('enddate')),
            'end_date': parse_date(dingtalk_data.get('enddate')),
            'source': 'dingtalk',
            # 钉钉特有字段 - 直接映射
            'project': dingtalk_data.get('project'),
            'depart': dingtalk_data.get('depart'),
            'days': dingtalk_data.get('days'),
            'bid': dingtalk_data.get('bid'),
            'addr': dingtalk_data.get('addr'),
            'details': dingtalk_data.get('details')
        }
        
        return transformed
    
    @action(detail=True, methods=['patch'])
    def update_dates(self, request, pk=None):
        """更新试验时间（用于甘特图拖拽）"""
        instance = self.get_object()
        
        if not instance.is_adjustable:
            return Response({
                'error': '该试验计划不允许调整时间'
            }, status=status.HTTP_400_BAD_REQUEST)
        
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
